7章 スコープ
https://gyazo.com/51ba590422cb6160a4960060509a9941
スコープ: 変数、定数および引数が有効な範囲を決めるもの
code:js
function f(x) {
reuturn x + 3;
}
console.log(f(5)); // 8
console.log(x); // ReferenceError: x is not defined
上のようなとき、「xのスコープは関数fである」という。
関数の仮引数は関数が呼び出されるまでは存在していない
関数は呼び出されるたびに引数の実体が生成される
呼び出された関数から制御が戻るときにスコープから消える
変数と定数は生成されるまでは存在していない
let、constで宣言されるまではスコープにはない
varは特別
7.1 スコープと存在
存在とスコープは分けて考えなければならない
変数が存在しなければスコープ内にない
スコープ内になければ変数が存在しないとは限らない
スコープ:「現在実行中のプログラムの実行部分(実行コンテキスト)で見えていてアクセスできる識別子の集合」とみることもできる
存在:メモリが確保されて何かを保持している識別子であるかどうか
変数などがプログラム中で存在している状態ではなくなっても、JavaScriptがその分のメモリをすぐに回収するとは限らない
その変数などに関して管理を行う必要がなくなったことを記録するだけ
実際のメモリの回収はガベージコレクションによって定期的に行われる
処理に余裕がある場合に自動的に行われる
7.2 静的スコープと動的スコープ
あるソースコードを見るとき、静的な構造(lexical structure)を見ていることになる。
プログラムの実行は上から下に行われるとは限らない
JavaScriptのスコープは静的スコープ(構文スコープ: lexical scope)
どの変数がスコープにあるかはソースコードを見ることで決めることができる
code:js
const x = 3;
function f() {
console.log(x); // 3
console.log(y); // ReferenceError: y is not defined
}
f();
const y = 3;
変数xは関数fが定義されるときに存在しているがyは存在していない
fが呼び出されたときにその本体の中ではxはスコープにあるがyはスコープにはない
JavaScriptにおける静的スコープはグローバルスコープ、ブロックスコープ、関数スコープに対して適用される
7.3 グローバルスコープ
スコープは階層的
グローバル(大域)スコープ: 階層構造の根本。プログラム実行開始時に暗黙的に存在するスコープ
JavaScriptのプログラムが実行を開始し、関数を呼び出す前にはグローバルスコープで実行される
グローバルスコープで宣言したものはすべてのスコープで利用可能
グローバル変数はすべてのスコープ利用可能なので注意が必要
どの関数でもグローバル変数の値が変更可能
名前衝突
大きなプログラムでは把握が難しいのでグローバル変数に依存しないようにすることが大切
7.4 ブロックスコープ
letとconstは識別子をブロックスコープで定義する
code:js
console.log('before block');
{
console.log('inside block);
const x = 3;
console.log(x);
}
console.log(outside block: ${x});
// 実行
// before block
// inside block
// 3
// ReferenceError: x is not defined
ブロックは上のように独立ブロックとして使うこともできる
7.5 変数の隠蔽
同じ名前の変数や定数が異なるスコープで利用された場合
スコープに交わりがない場合は単純
スコープが入れ子になった場合
code:js
{
let x = 'blue';
console.log(x); // blue
{
let x = 3;
console.log(x); // 3
}
console.log(x); // blue
}
console.log(typeof x); // undefined
変数のマスキング(隠蔽: shadowing)
内側のブロックと外側のブロックのxは別の変数
内側のxが外側のxをマスクすることになる
2つの変数はどちらもスコープに入るが同じ名前なので外側の変数にアクセスすることはできなくなる
code:js
{ /* 外側のブロックの始まり */
let x = { color: "青" };
let y = x; /* yとxが同じオブジェクトを参照する */
let z = 3;
{ /* 内側のブロックの始まり */
let x = 5; /* 外側のxが「マスク」される(隠されてしまう) */
console.log(x); // 5
console.log(y.color); // 青 (yによって参照されているオブジェクトは相変わらずスコープに入っている。外側のブロックのxもスコープには入っている)
y.color = "赤";
console.log(z); // 3 (zはマスクされていない)
} /* 内側のブロックの終わり */
console.log(x.color); // 赤 (内側のスコープでオブジェクトに変更がなされた)
console.log(y.color); // 赤 (xとyは同じオブジェクトを参照している)
console.log(z); // 3
} /* 外側のブロックの終わり */
新しいスコープに入るのは必ずしも以前のスコープから抜ける必要はない
どの変数がスコープ内にあるかを決定するスコープチェインを形成する
7.6 関数、クロージャ、静的スコープ
関数は定義される場所と使われる場所が異なるのでスコープも複雑になる
従来: すべての関数をグローバルスコープで定義、関数内でグローバルスコープへの参照を避ける
モダンなJavaScript開発では関数は必要なときにその場で定義される傾向にある
クロージャ(closure)と呼ばれる特殊なスコープで定義された関数もよく使われる
クロージャは関数の周囲にスコープを閉じ込む(closeする)もの
code:js
let globalFunc;
{
let blockVar = 'a';
globalFunc = function() {
console.log(blockVar);
}
}
globalFunc(); // a
globalFuncはひとつのブロック内で値を代入されている
そのブロックがクロージャを形成している
globalFuncをどこから呼び出したとしても、この関数はこのクロージャ内の識別子にアクセスすることができる
globalFuncを呼び出すときにblockVarへのアクセスが可能
すでにそのスコープから抜けているのにもかかわらず、ということが重要
通常はあるスコープから抜けてしまえばそのスコープ内で宣言された変数はその存在がなくなる
JavaScriptではこのようにスコープを関数が保持しておくことができる。
code:js
let f; /* 未定義のグローバルな変数 */
{
let o = { note: '安全', note2: '大丈夫' };
f = function() {
console.log("無名関数の中: " + o.note); // 無名関数の中: 安全
return o;
}
}
let oRef = f();
console.log(oRef); // { note: '安全', note2: '大丈夫' }
oRef.note = "まったく安全ではない!";
console.log(oRef); // { note: 'まったく安全ではない!', note2: '大丈夫' }
通常、スコープ外のものにはアクセスできない。
しかし関数を使うと例外的にのぞき窓を作ることができる
7.7 IIFE - 即座に実行される関数式
関数式を使って即座に実行される関数式(IIFE: Immediately Invoked Function Expression)を作ることができる
code:js
(function() {
// IIFEの本体
})();
IIFEのメリット
その内部が独自のスコープを持っていること
それは関数なのでスコープの外に渡すことができる
ES2015ではブロックスコープの変数が導入されたためIIFEの必要性が少し低くなりはしたが使いみちはまだたくさんある
クロージャーを生成してその中の何かを返したいようなとき
7.8 関数のスコープと巻き上げ
varによって宣言された変数は関数スコープを持つ
宣言された関数全体で有効になる
「varを使って宣言されたグローバル変数」と「関数内でvarで宣言されずに使われた変数」は同じふるまいをする
varを使って宣言した場合は現在のスコープのどこでも利用可能になる
letを使って変数を宣言した場合は宣言するまでは存在しない
varを使って宣言した変数には巻き上げ(hoisting)というメカニズムが作用する
スコープ全体を見回してvarで宣言された同じ名前の変数があれば、それが使われている箇所で宣言されたものとして扱ってしまう
巻き上げられるのは宣言だけであって、値の代入も巻き上げられるわけではない
宣言されていない変数はエラーになるが、varは宣言の前でも参照可能(値はundefined)
varは宣言を繰り返してもエラーにならない
varが新しい変数を作るためには使えない
letの場合は内側のブロックで使われると新しい変数が生成される
letを使えるならvarを使う理由はない
varの巻き上げについて理解する理由
古いコードがまだ残っている
関数の宣言も巻き上げられる
7.9 関数の巻き上げ
関数宣言もスコープの先頭に巻き上げられる。
関数の宣言の前に呼び出すことができる。
変数に代入された関数式は巻き上げられないことに注意
7.10 TDZ(Temporal Dead Zone)
TDZ(Temporal Dead Zone): 「letで宣言された変数は宣言されるまでは存在しない」ということを表す表現
ES2015以前はtypeofで宣言されていたかを調べていたが、letでは使えない。
7.11 strictモード
ES5までの構文を使うと暗黙的グローバルとでも呼ぶべき変数ができてしまう
strictモードを使えば暗黙的グローバルは禁止される。
'use strict';
グローバルスコープの他の文の前にあればスクリプト全体
関数内の他の文の前にあればその関数内
グローバルスコープで指定するときは注意が必要
現在のWebサイトでは複数のスクリプトを一緒に使うのが一般的だが、全ファイルに適用されてしまう
したがって、グローバルスコープでstrictモードを利用するのは必ずしも推奨できる方法ではない
自分の書く関数についてはすべてstrictモードにしたいという場合は全体のコードを一つの関数としてラップしてしまう方法がある
code:js
(function() {
'use strict';
// 自分のコード
})();
strictモードやlintプログラムは問題が起こるのを事前に防いでくれる。